#define NOMINMAX
#include "windows.h"
#include "NxPhysics.h"
#include "NxControllerManager.h"
#include "NxCapsuleController.h"
#include "Engine.h"
#include "PlayerController.h"

namespace FinalRendering
{
	namespace Engine
	{
		namespace Physics
		{
			class ControllerHitReport : public NxUserControllerHitReport
			{
			private:
				PlayerController * ctrl;
			public:
				ControllerHitReport(PlayerController * _ctrl)
					: ctrl(_ctrl)
				{}

				NxControllerAction  onShapeHit (const NxControllerShapeHit &hit)
				{
					return NX_ACTION_NONE;
				}

				NxControllerAction  onControllerHit (const NxControllersHit &hit)
				{
					return NX_ACTION_NONE;
				}


			};

			PlayerController::PlayerController(Engine * _engine, NxScene * scene, const vec3 & initPos)
				: engine(_engine), ySpeed(0.0f), jumpElapsedTime(0.0f), CollisionDown(false),
				CollisionUp(false), CollisionSide(false)
			{
				jumping = false;

				MoveSpeed = 128.0f;
				CrouchedMoveSpeed = 64.0f;
				DampAcceleration = 512.0f;
				MoveAcceleration = 1024.0f;
				GravityAcceleration = 640.0f;
				JumpSpeed = 256.0f;

				hitReport = new ControllerHitReport(this);

				NxCapsuleControllerDesc desc;
				desc.radius = 8.0f;
				desc.height = 72.0f - desc.radius*2;
				desc.stepOffset = 18.0f;
				
				desc.slopeLimit = 0.707f;
				desc.position.x = initPos.x;
				desc.position.y = initPos.y;
				desc.position.z = initPos.z;
				
				desc.callback = hitReport;

				ctrl = engine->GetControllerManager()->createController(scene, desc);
			}

			PlayerController::~PlayerController()
			{
				engine->GetControllerManager()->releaseController(*ctrl);
				delete hitReport;
			}
			
			vec3 PlayerController::GetPosition()
			{
				vec3 pos;
				NxExtendedVec3 v = ctrl->getPosition();
				pos.x = (float)v.x;
				pos.y = (float)v.y;
				pos.z = (float)v.z;
				pos.y += 30.0f;
				return pos;
			}

			void PlayerController::MoveDirection(const vec3 & dir, float dTime)
			{
				// Damp current speed;
				float curSpeedLen = curSpeed.GetLength2();
				if (curSpeedLen <= DampAcceleration * DampAcceleration * dTime * dTime)
				{
					curSpeed = vec3(0.0f,0.0f,0.0f);
					curSpeedLen = 0.0f;
				}
				else if (CollisionDown)
				{
					curSpeedLen = sqrt(curSpeedLen);
					float invCurSpeedLen = 1.0f/curSpeedLen;
					curSpeedLen -= DampAcceleration*dTime;
					curSpeed *= (invCurSpeedLen*curSpeedLen);
				}
				// Accelerate
				if (CollisionDown)
				{
					curSpeed += dir * (MoveAcceleration * dTime);
					float spdLen = curSpeed.GetLength();
					if (spdLen > MoveSpeed)
					{
						curSpeed *= MoveSpeed/spdLen;
					}
				}
				// Jumping or Falling
				if (CollisionDown)
				{
					jumpElapsedTime = 0.0f;
					if (ySpeed < 0.0f)
						ySpeed = 0.0f;
				}
				else if (CollisionUp)
				{
					jumpElapsedTime = 0.0f;
					if (ySpeed > 0.0f)
						ySpeed = 0.0f;
				}
				
				{
					jumpElapsedTime += dTime;
					ySpeed -= GravityAcceleration * dTime;
					curSpeed.y = ySpeed;
				}
				unsigned int cflag;
				NxVec3 disp;
				disp.x = curSpeed.x * dTime;
				disp.y = curSpeed.y * dTime;
				disp.z = curSpeed.z * dTime;
				if (jumping)
				{
					vec3 dir;
					Normalize(curSpeed, dir);
					disp.x += dir.x * MoveSpeed * dTime;
					disp.z += dir.z * MoveSpeed * dTime;
				}
				ctrl->move(disp, 0xFFFFFFFF, 0.001f, cflag, 1.0f);
				CollisionDown = (cflag & NXCC_COLLISION_DOWN)!=0;
				CollisionUp = (cflag & NXCC_COLLISION_UP)!=0;
				CollisionSide = (cflag & NXCC_COLLISION_SIDES)!=0;
				if (CollisionDown)
					jumping = false;
			}

			bool PlayerController::IsOnGround()
			{
				return CollisionDown;
			}
			
			void PlayerController::Jump()
			{
				if (CollisionDown)
				{
					jumping = true;
					ySpeed = JumpSpeed;
				}
			}
		}
	}
}